/*
 * $Id: htmllib.c,v 1.2 1995/03/31 01:54:19 jay Exp $
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "atom.h"
#include "dtd.h"
#include "html.h"
#include "htmlloc.h"
#include "entity.h"

static struct error globalerr;
static int strCASEcmp(char *s1, char *s2);

/*
 * Externally visible functions.
 */

/*
 * Set up for parsing.
 */
void
HTMLParseBegin()
{
    extern int yydebug;

    InitDTD();
    atomPRE = GetAtom("PRE");
    atomHTML = GetAtom("HTML");
    atomHEAD = GetAtom("HEAD");
    atomTITLE = GetAtom("TITLE");
    atomBODY = GetAtom("BODY");
    current = _initTree();
    yydebug = 0;
}

/*
 * Actually parse from file
 */
void
HTMLParseDo(FILE *yyin)
{
    while (!feof(yyin)) {
	yyparse();
    }
}

/*
 * Do final checking and return pointer to top of parse tree
 */
Block *
HTMLParseEnd()
{
    Block *top = current->top;

    /* Make sure a html has been put in place. */
    if (HTMLGetBParent(current->html) == 0) {
	_insertBlock(current->top, current->html);
	HTMLSetBParent(current->html, current->top);
	HTMLAddBFlags(current->html, PRETTYSTART | PRETTYEND);
    }

    /*
     * Make sure a head has been put in place.
     * We will always insert the head as the first object under
     * html in this case.
     */
    if (HTMLGetBParent(current->head) == 0) {
	HTMLSetBSib(current->head, HTMLGetBChild(current->html));
	HTMLSetBChild(current->html, current->head);
	HTMLSetBParent(current->head, current->html);
	HTMLAddBFlags(current->head, PRETTYSTART | PRETTYEND);
    }

    /* Make sure a title has been put in place. */
    if (HTMLGetBParent(current->title) == 0) {
	struct error err;

	_insertBlock(current->head, current->title);
	HTMLSetBParent(current->title, current->head);
	HTMLAddBFlags(current->title, PRETTYEND);

	err.lineno = yygetlineno();
	err.severity = E_WARNING;
	err.text = "<TITLE>";
	err.type = E_MISSINGTITLE;
	err.message = "No <TITLE> in document";
	_errorHandler(&err);
    }

    /* Make sure a body has been put in place. */
    if (HTMLGetBParent(current->body) == 0) {
	_insertBlock(current->html, current->body);
	HTMLSetBParent(current->body, current->html);
	HTMLAddBFlags(current->body, PRETTYSTART | PRETTYEND);
    }

    free(current);
    return (top);
}

void
HTMLFreeParseTree(Block *old)
{
    HTMLFreeTree(old);
}

/*
 * Set a new error handler.
 * See html.h for information on the data passed into the handler
 */
HTMLErrorHandler
HTMLSetErrorHand(HTMLErrorHandler newHandler)
{
    HTMLErrorHandler old = _errorHandler;

    _errorHandler = newHandler;
    return (old);
}

/*
 * This function creates and fills in a new block with basic information.
 * The type should be PCDATA, CMNT, or TAG.  If no line number is available
 * -1 should be passed.  Data should contain the string associated with
 * the type:  The comment text in the case of a comment, the tag in the
 * case of a tag, and a text string for type PCDATA.
 * In the case of TAG, data will be used to look up the tag in the
 * HTML elements list and set the right type.
 */
Block *
HTMLNewBlock(int type, char *data, int lineno)
{
    Block *newBlock;
    struct error err;
    char buf[MAX_ERROR_LEN];

    newBlock = (Block *)calloc(1, sizeof(Block));
    if (newBlock == NULL) {
	struct error err;

	err.lineno = lineno;
	err.severity = E_CRASH;
	err.text = data;
	err.type = E_NOMEM;
	err.message = _NoMem;
	_errorHandler(&err);
	exit(2);
    }
    HTMLSetBType(newBlock, type);
    switch (type) {
	case PCDATA:
	    HTMLSetBData(newBlock, data);
	    HTMLSetBLineno(newBlock, lineno);
	    HTMLSetBAtom(newBlock, GetAtom("PCDATA"));
	    break;
	case CMNT:
	    HTMLSetBData(newBlock, data);
	    HTMLSetBLineno(newBlock, lineno);
	    break;
	case TAG:
	    HTMLSetBData(newBlock, data);
	    HTMLSetBLineno(newBlock, lineno);
	    HTMLSetBAtom(newBlock, GetAtom(data));
	    if (GetElementInfo(HTMLGetBAtom(newBlock)) == 0) {
		err.lineno = lineno;
		err.severity = E_WARNING;
		err.text = data;
		err.type = E_UNKNOWNELE;
		sprintf(buf, "Unknown tag name: <%s>", data);
		err.message = buf;
		_errorHandler(&err);
	    }
	    break;
	default:
	    err.lineno = lineno;
	    err.severity = E_SEVERE;
	    err.text = data;
	    err.type = E_PROGRAMMING;
	    sprintf(buf, "Unknown block type being allocated: <%d>", type);
	    err.message = buf;
	    _errorHandler(&err);
	    break;
    }
    return (newBlock);
}

void
HTMLFreeBlock(Block *old)
{
    if (old->data)
	free(old->data);
    if (old->attrs)
	HTMLFreeAttributeList(old->attrs);
    free(old);
}

void
HTMLFreeTree(Block *old)
{
    if (old->children)
	HTMLFreeTree(old->children);
    if (old->next_sib)
	HTMLFreeTree(old->next_sib);
    HTMLFreeBlock(old);
}

Attribute *
HTMLNewAttr(char *name, char *value, int lineno)
{
    Attribute *newAttribute;
    struct error err;
    char buf[MAX_ERROR_LEN];

    newAttribute = (Attribute *)calloc(1, sizeof(Attribute));
    if (newAttribute == NULL) {
	struct error err;

	err.lineno = lineno;
	err.severity = E_CRASH;
	err.text = name;
	err.type = E_NOMEM;
	err.message = _NoMem;
	_errorHandler(&err);
	exit(2);
    }
    HTMLSetAName(newAttribute, name);
    HTMLSetAData(newAttribute, value);
    HTMLSetALineno(newAttribute, lineno);
    HTMLSetANext(newAttribute, 0);
    HTMLSetAAtom(newAttribute, GetAtom(name));
    /*
     * XXX Need to redo this to just warn about a new atom name
     * It duplicates behavior in verifyAttrs
     */
    if (HTMLGetAAtom(newAttribute) == -1) {
	err.lineno = lineno;
	err.severity = E_WARNING;
	err.text = name;
	err.type = E_UNKNOWNATTR;
	if (value)
	    sprintf(buf, "Unknown attribute name: %s=%s", name, value);
	else
	    sprintf(buf, "Unknown attribute name: %s", name);
	err.message = buf;
	_errorHandler(&err);
    }
    return (newAttribute);
}

void
HTMLFreeAttribute(Attribute *old)
{
    if (old->name)
	free(old->name);
    if (old->data)
	free(old->data);
    free(old);
}

void
HTMLFreeAttributeList(Attribute *old)
{
    if (old->next)
	HTMLFreeAttributeList(old->next);	/* Recurse */
    HTMLFreeAttribute(old);
}

char *
HTMLGetAttr(Block *bp, char *name)
{
    Attribute *ap = HTMLGetBAttrs(bp);

    while (ap) {
	if (strCASEcmp(ap->name, name) == 0) {
	    if (ap->data)
		return (ap->data);
	    else
		return (ap->name);		/* XXX Cheesy */
	}
	else {
	    ap = ap->next;
	}
    }
    return (0);
}

int
HTMLRemoveAttr(Block *bp, char *name)
{
    Attribute *ap = HTMLGetBAttrs(bp);

    if (ap) {
	if (strCASEcmp(ap->name, name) == 0) {	/* first on list */
	    HTMLSetBAttrs(bp, ap->next);
	    HTMLFreeAttribute(ap);
	    return (1);
	}
	while (ap->next) {
	    if (strCASEcmp(ap->next->name, name) == 0) {
		Attribute *a1 = ap->next;

		HTMLSetANext(ap, ap->next->next);
		HTMLFreeAttribute(a1);
		return (1);
	    }
	    ap = ap->next;
	}
    }
    return (0);
}

void
HTMLSetAttr(Block *bp, char *name, char *data)
{
    Attribute *newAttr, *ap = HTMLGetBAttrs(bp);

    while (ap) {
	if (strCASEcmp(ap->name, name) == 0) {
	    ap->data = data;		/* Change the data */
	    return;
	}
	else {
	    ap = ap->next;
	}
    }
    /* Build attribute */
    newAttr = (Attribute *)calloc(1, sizeof(Attribute));
    if (newAttr == NULL) {
	struct error err;

	err.lineno = -1;
	err.severity = E_CRASH;
	err.text = name;
	err.type = E_NOMEM;
	err.message = _NoMem;
	_errorHandler(&err);
	exit(2);
    }
    newAttr->name = name;
    newAttr->data = data;
    newAttr->next = HTMLGetBAttrs(bp);		/* Add the new attribute */
    HTMLSetBAttrs(bp, newAttr);
}

/*
 * Remove a block from the tree
 * Although this function fixes up the tree with respect to this
 * block's siblings, it can't deal with the block's children.  A
 * pointer to the children is returned.
 */
Block *
HTMLRemoveBlock(Block *bp)
{
    Block *pp = HTMLGetBParent(bp);

    if (pp == 0) {
	struct error err;

	err.lineno = -1;
	err.severity = E_SEVERE;
	err.text = HTMLGetBData(bp);
	err.type = E_PROGRAMMING;
	err.message = "HTMLRemoveBlock() called on block not in tree";
	_errorHandler(&err);
	return (0);
    }
    /*
     * Search the parent for the block that points to this one
     */
    if (HTMLGetBChild(pp) == bp) {
	HTMLSetBChild(pp, HTMLGetBSib(bp));
    }
    else {
	Block *tmp = HTMLGetBChild(pp);

	while (HTMLGetBSib(tmp) && (HTMLGetBSib(tmp) != bp))
	    tmp = HTMLGetBSib(tmp);
	/*
	 * If the following happens, it means the tree is trashed.
	 * This is bad.
	 */
	if (HTMLGetBSib(tmp) == 0) {
	    struct error err;

	    err.lineno = -1;
	    err.severity = E_SEVERE;
	    err.text = HTMLGetBData(bp);
	    err.type = E_PROGRAMMING;
	    err.message = "HTMLRemoveBlock() called with block"
			  " not parent's children list";
	    _errorHandler(&err);
	    return (0);
	}
	HTMLSetBSib(tmp, HTMLGetBSib(bp));	/* remove it from the list */
    }
    return (bp->children);
}

void
HTMLInsertBlock(Block *bp, Block *parent, Block *predecessor)
{
    if (predecessor == NULL) {
	HTMLSetBSib(bp, HTMLGetBChild(parent));
	HTMLSetBChild(parent, bp);
	HTMLSetBParent(bp, parent);
	return;
    }
    HTMLSetBSib(bp, HTMLGetBSib(predecessor));
    HTMLSetBSib(predecessor, bp);
    HTMLSetBParent(bp, HTMLGetBParent(predecessor));
    return;
}

static int
strCASEcmp(char *s1, char *s2)
{
    char *cp, word1[76], word2[76];		/* Token sizes are limited */
    int i;

    cp = word1;
    while (*s1)
	*cp++ = toupper(*s1++);
    *cp = '\0';
	
    cp = word2;
    while (*s2)
	*cp++ = toupper(*s2++);
    *cp = '\0';
    return strcmp(word1, word2);
}
